<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
		<title>对象_原型_继承</title>
	</head>
	<body>
		注解：person是构造函数Person的实例对象。

		1.hasOwnProperty():
		-- 用于确定某个实例属性或方法是在实例上还是在原型对象上,在实例上返回true，它会忽略那些从原型链上继承的属性；
		-- 语法：object.hasOwnProperty('属性名称')、如person.hasOwnProperty("name")；

		2.in 操作符：
		-- 无论查找的属性或方法是在原型上还是在实例上都返回true；
		-- 语法 ：属性名 in 实例对象、如： "name" in person;

		3.isPrototypeOf():
		-- 检测一个对象是否存在另一个对象的原型链上（判断要检查其原型链的对象是否存在于指定对象实例中，是返回true，否返回fals）；
		-- 语法：object1.isPrototypeOf(Object2)、如：Person.prototype.isPrototypeOf(person)；
		
		4.instanceof 
		-- 检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
        -- 语法：object instanceof constructor  ， object为某个实例对象，constructor为某个构造函数


	</body>
	<script type="text/javascript">
		/*一、工厂模式：
		1.1没有解决对象标识问题（即新创建的对象是什么类型）
		*/
		function createObj(name, age) {
			let o = new Object();
			o.name = name;
			o.age = age;
			return o
		}


		let getObj1 = createObj("Greg", 27);
		let getObj2 = createObj("诸葛亮", 26);
		console.log(getObj1 instanceof createObj) //false
		console.log(getObj2 instanceof createObj) //false


		/*二、
		2.1构造函数模式：
		无参可省去括号：let person1 = new Person()等同于let person2 = new Person;
		a.优势：相比于工厂模式、可以区分实例是来自哪个构造函数；
		b.问题：其定义的方法会在每个实例上都创建一遍:下面person1 和 person2 都有名为 sayName()的方法，
		       但这两个方法不是同一个 Function 实例,因此,person1.sayName != person2.sayName
		*/
		function Person(name, age) {
			this.name = name;
			this.age = age;
			this.sayName = function() {
				console.log(this.name);
			};
		}

		function Tree() {};

		let person1 = new Person("Greg1", 27);
		let person2 = new Person("Tom", 27);
		let person3 = new Tree();
		console.log(person1 instanceof Object); // true ，person1被认为是Object实例，是因为所有自定义对象都继承自Object

		console.log(person1 instanceof Person) //true
		console.log(person2 instanceof Person) //true
		console.log(person3 instanceof Person) //false

		/*
		2.2：解决构造函数 ：
		其定义的方法会在每个实例上都创建一遍问题 :
		这样虽然解决了相同逻辑的函数重复定义的问题，但全局作用域也因此被搞乱了，因为那个函数实际上只能在一个对象上调用。如果这个对象需要多个方法,
		那么就要在全局作用域中定义多个函数。这会导致自定义类型引用的代码不能很好地聚集一起。这个新问题可以通过原型模式来解决。
		*/
		function Person(name, age) {
			this.name = name;
			this.age = age;
			this.sayName = sayName
		}

		function sayName() {
			console.log(99, this.name);
		};
		let personTest = new Person("Greg1", 27);
		person66.personTest('诸葛', 20)


		/* 三、
		3.1原型模式
		 a.每个函数都会创建一个 prototype 属性，这个属性是一个对象，包含应该由特定引用类型的实例共享的属性和方法。对象就是通过调用构造函数创建的对象的原型
         b.解决构造函数存在的问题:使用这种原型模式定义的属性和方法是由所有实例共享的,其定义的方法不会重复创建
		 c.原型存在的问题：无法传递初始化参数，导致所有实例默认都取得相同的属性值，当有引用值的属性时，其中一个实例修改原型上这个引用值时另外一个实例原型也会跟着改变
		 */
		function Person() {}
		Person.prototype.name = '诸葛';
		Person.prototype.sayName = function() {
			console.log(this.name)
		}
		let person1 = new Person();
		let person2 = new Person();
		console.log(person1.sayName == person2.sayName); // true

		/*
		3.2原型模式--重写原型:
		a.可以看到、每次定义一个属性或方法都会把 Person.prototype 重写一遍
		b.为了减少代码冗余,因此：
		*/
		function Person() {}
		Person.prototype = {
			name: "Nicholas",
			sayName() {
				console.log(this.name);
			}
		};
		// 这样重写之后，Person.prototype 的 constructor 属性就不指向 Person，而是指向了Object,解决这个问题，可以：
		Person.prototype = {
			name: "Nicholas",
			constructor: Person,
			sayName() {
				console.log(this.name);
			}
		};


		/*
		3.3原型模式--重写原型之原型的动态性:
		未重写原型之前，无论是先实例，还是先给原型添加属性和方法，实例都可以访问到原型上的属性方法
		*/
		let friend = new Person();
		Person.prototype.sayHi = function() {
			console.log("hi");
		};
		friend.sayHi(); // "hi"，可以访问到，没问题！

		/*
		3.4原型模式--重写原型之原型的动态性:
		重写原型之后，如果先实例，这个实例不能访问到原型上的属性方法，重写整个原型会切断最初原型与构造函数的联系
		*/
		let friend = new Person();
		Person.prototype = {
			constructor: Person,
			name: "Nicholas",
		};
		friend.sayName(); // 错误，无法访问到


		/* 四、继承：
		 javascript的继承是通过原型链实现
		*/
		/* 4.1、继承之原型链：
		a.原型链概念：利用原型让一个引用类型继承另一个引用类型的属性和方法。然后层层递进，就构成了实例与原型的链条，这就是所谓原型链。
		b.原型链存在问题：
		  --子类型在实例化时不能给父类型的构造函数传参;
		  --原型中包含引用值的时候，引用类型在实例中指向同一个地址，无法独立
		*/
		function Parent() {
			this.colors = ['red', 'green']
		}

		function Son() {}
		Son.prototype = new Parent();
		let son1 = new Son();
		son1.colors.push('blue');
		console.log(son1.colors) //["red", "green", "blue"]
		let son2 = new Son();
		console.log(son2.colors) //["red", "green", "blue"]


		/* 4.2、盗用构造函数：
		-- 解决原型包含引用值导致的继承问题
		-- 缺点：须在构造函数中定义方法、函数不能重用、子类也不能访问父类原型上定义的方法
		*/
		function Parent(name = '诸葛亮') {
			this.name = name;
			this.colors = ['red', 'green']
		};
		Parent.prototype.sayName = function() {
			console.log(this.name)
		};

		function Son() {
			Parent.call(this, '刘备')
		}

		let son1 = new Son();
		son1.colors.push('blue');
		console.log(son1.sayName()); //undefined


		/* 4.3、组合继承(伪经典继承)：
		-- 解决盗用构造函数继承问题
		-- 组合继承弥补了原型链和盗用构造函数的不足，是 JavaScript 中使用最多的继承模式。而且组合继承也保留了 instanceof 操作符和 isPrototypeOf() 方法识别合成对象的能力
		*/

		function Parent(name = '诸葛亮') {
			this.name = name;
			this.colors = ['red', 'green']
		};
		Parent.prototype.sayName = function() {
			console.log(this.name)
		};

		function Son(name) {
			// 继承属性
			Parent.call(this, name)
		}
		// 继承方法
		Son.prototype = new Parent();
		let son1 = new Son('刘备');
		son1.colors.push('blue');
		console.log(son1.sayName()); //刘备


		/* 4.4、原型式继承：
		-- 本质是执行了一次浅复制，和原型链存在问题的是一样的，问题见：四、4.1	
		-- 下面是例子：可以看到，最后他们引用类似对象是同一个了，引用对象继承无法各自独立
		-- 自定义的object()方法等同于es5新增的Object.create() 方法
		*/

		function object(o) {
			function F() {}
			F.prototype = o;
			return new F();
		}

		let person = {
			name: "Nicholas",
			friends: ["Shelby", "Court", "Van"]
		};
		let anotherPerson = object(person);

		anotherPerson.name = "Greg";
		anotherPerson.friends.push("Rob");

		let yetAnotherPerson = object(person);

		yetAnotherPerson.name = "Linda";
		yetAnotherPerson.friends.push("Barbie");

		console.log(person.friends); // "Shelby,Court,Van,Rob,Barbie" 

		/* 4.5、寄生式继承：
		-- 通过寄生式继承给对象添加函数会导致函数难以重用，与构造函数模式类似
		*/


		/* 4.6、寄生式组合继承：
		-- 寄生式组合继承通过盗用构造函数继承属性，但使用混合式原型链继承方法，基本思路是不通过调用父类构造函数给子类原型赋值，而是取得父类原型的一个副本
		--子类原型最终包含超类对象的所有实例属性，子类构造函数只要在执行时重写自己的原型就行了
		*/
		// 4、寄生组合式继承
		function inheritPrototype(subType, superType) {
			var myprototype = Object(superType.prototype); //创建对象
			myprototype.constructor = subType; //增强对象
			subType.prototype = myprototype; //指定对象
		}

		function SuperType(name) {
			this.name = name;
			this.colors = ["red", "blue", "green"];
		}
		SuperType.prototype.sayName = function() {
			console.log(this.name);
		};

		function SubType(name, age) {
			SuperType.call(this, name);
			this.age = age;
		}
		inheritPrototype(SubType, SuperType);
		SubType.prototype.sayAge = function() {
			console.log(this.age);
		};

		let sub1 = new SubType('诸葛亮', 36)
		console.log(sub1);
	</script>
</html>
